<?php
class IDGenter
{/*{{{*/
    private $_executer;
    private $_idsets = array() ;
    private $_ch;
    private $_objName;
    private $_lockIns;

    const BATCH_GET_ID_NUM = 100;

    const DEFAULT_KEY = 'other';

    static public function getInstance()
    {/*{{{*/
        static $ins;
        if (false == $ins instanceof self)
        {
            $ins = new self();
        }
        return $ins;
    }/*}}}*/

    public function setDbDriver($executer)
    {
        $this->_executer = $executer;
    }

    public function setCacheHelper($cacheHelper)
    {/*{{{*/
        $this->_ch = $cacheHelper;
    }/*}}}*/

    private function __construct()
    {/*{{{*/
    }/*}}}*/

    private function readIDSet()
    {/*{{{*/
        //echo "\nload from db\n";
        $cmd = "select * from idgenter where obj = '$this->_objName';";

        $options['db'] = $this->_executer;
        $this->_lockIns = LockUtil::factory(LockUtil::LOCK_TYPE_DB, $options);
        $this->_lockIns->getLock($this->_objName);

        $stmt = $this->_executer->query($cmd);
        $row = $stmt->fetch();

        if (empty($row))
        {
            throw new SysException('no such objeName['.$this->_objName.']');
        }

        $this->_idsets[$row['obj']]['curID'] = $row['id'];
        $this->_idsets[$row['obj']]['maxID'] = $row['id'];
        $this->_idsets[$row['obj']]['step'] = $row['step'];
    }/*}}}*/

    private function loadIDSet()
    {/*{{{*/
        if (false == isset($this->_idsets[$this->_objName]))
        {
            //echo "\nload from cache\n";
            $this->_idsets[$this->_objName] = $this->_ch->get($this->_objName);
        }
        return empty($this->_idsets[$this->_objName]);
    }/*}}}*/

    private function cacheIDSet()
    {/*{{{*/
        $this->_ch->set($this->_objName, $this->_idsets[$this->_objName]);
    }/*}}}*/

    public function createID($objName=self::DEFAULT_KEY)
    {/*{{{*/
        if ($this->_objName != $objName)
        {
            $this->_objName = $objName;
        }

        $this->_fileLockIns = LockUtil::factory(LockUtil::LOCK_TYPE_FILE, $options=array('path'=>'/tmp/'));
        $this->_fileLockIns->getLock($this->_objName);
        if ($this->loadIDSet())
        {
            $this->readIDSet();
        }
        $res = $this->createIDimp();
        $this->_fileLockIns->releaseLock($this->_objName);
        return $res;
    }/*}}}*/

    private function createIDimp()
    {/*{{{*/
        if ($this->_idsets[$this->_objName]['curID'] == $this->_idsets[$this->_objName]['maxID'] )
        {
            if(false == $this->getBatchID())
                return false;
        }
        $createdID = ++$this->_idsets[$this->_objName]['curID'];

        $this->cacheIDSet();
        return $createdID;
    }/*}}}*/

    private function getBatchID()
    {/*{{{*/
        //echo "\nload ids\n";

        $step = $this->_idsets[$this->_objName]['step'];
        $cmd = "UPDATE idgenter SET id = LAST_INSERT_ID(id + $step) where obj= '$this->_objName';";

        if ($this->_executer->query($cmd))
        {
            $lastId = $this->_executer->lastInsertId();
        }
        $this->_lockIns->releaseLock($this->_objName);

        if (0 < $lastId)
        {
            $this->_idsets[$this->_objName]['step'] = $step;
            $this->_idsets[$this->_objName]['maxID'] = $lastId;
            $this->_idsets[$this->_objName]['curID'] = $lastId - $step;
            return true;
        }
        return false;
    }/*}}}*/
}/*}}}*/

class CacheHelperStub implements Cachable
{
    public function get($property){}
    public function set($property, $value, $expire){}
    public function delete($property){}
}
